﻿using System;
using System.Linq;
using Biosek;
using Newtonsoft.Json.Linq;
using System.IO;
using System.Collections;
using System.Collections.Generic;

namespace PairSampler
{
	public class Program
	{
		struct wnd
		{
			public int prefix;
			public int suffix;
		}

		// llamar con los parametros 
		// "PairSampler.exe n:m inputFile outputFile [--out-arff] [--out-json] [--out-destino]"
		//   n es la longitud del prefijo de ventana
		//   m es la longitud del sufijo de ventana
		// El archivo de salida sera forzado a tener extension json o arff
		public static void Main(string[] args)
		{			
			// parsing de parametros
			var inputFilename = args[1];
			var outputFilename = args[2];

			wnd window;
			{
				var wnd_str = args[0];
				var wnd_int_split = wnd_str.Split(':').Select(x => int.Parse(x));
				if (wnd_int_split.Count() != 2) throw new InvalidOperationException("Bad formed parameter: " + wnd_str);
				window = new wnd { prefix = wnd_int_split.First(), suffix = wnd_int_split.Last() };
			}

			var flags = args.Skip(3).Select(x => x.ToLowerInvariant());
			var outputARFF = flags.Contains("--out-arff");
			var outputJSON = flags.Contains("--out-json");
			var outputDESTINO = flags.Contains("--out-destino");
			var outputCSV = flags.Contains("--out-csv");
			var testWindows = flags.Contains("--test-windows");

			// confirmacion
			Console.WriteLine("Window: prefix={0} suffix={1}", window.prefix, window.suffix);
			Console.WriteLine("Outputs: JSON={0} ARFF={1} DESTINO={2}", outputJSON, outputARFF, outputDESTINO);

			// carga archivo de entrada
			dynamic jsonInputLoaded = JsonSerializerHelper.LoadJsonFile(inputFilename);
			var negativeSamples = new ByteArraySet();
			var positiveSamples = new ByteArraySet();
			var overlaping = new ByteArraySet();

			var rawDataFromJson = jsonInputLoaded.data as IEnumerable;
			var rawObjects = rawDataFromJson.Cast<JObject>().Select(x => x.ToObject<SequenceSegmentPair>());
			var byteSequences = new List<byte[]>();
			if (!testWindows)
			{
				int? cdr;
				// Muestrea
				Sample(window, negativeSamples, positiveSamples, rawObjects, byteSequences, overlaping, out cdr);

				// Resuelve colisiones
				positiveSamples.UnionWith(overlaping);
				negativeSamples.ExceptWith(overlaping);

				Console.WriteLine("Collisions Resolved ({0})", overlaping.Count);
				Console.WriteLine("Positive set: {0}", positiveSamples.Count);
				Console.WriteLine("Negative set: {0}", negativeSamples.Count);

				var data = SamplerHelper.TagAndConcat(positiveSamples, negativeSamples);

				if (outputARFF)
				{
					outputFilename = Path.ChangeExtension(outputFilename, "arff");
					ARFFHelper.SavePlain(outputFilename, data);
				}
				if (outputJSON)
				{
					outputFilename = Path.ChangeExtension(outputFilename, "json");
					JsonSerializerHelper.SaveJsonFile(data.ToArray(), outputFilename);
				}
				if (outputDESTINO)
				{
					outputFilename = Path.ChangeExtension(outputFilename, "destino");
					DestinoHelper.SavePlain(outputFilename, data);
				}
				if (outputCSV)
				{
					outputFilename = Path.ChangeExtension(outputFilename, "csv");
					NumericCsvHelper.SavePlain(outputFilename, data);
				}
			}
			else
			{
				// comprueba todos los posibles tamaños de ventana para evaluar la cantidad de colisiones existentes
				var totalLenth = window.suffix + window.prefix;
				var report = new StreamWriter(outputFilename);
				int? cdr;
				report.WriteLine("length,prefix,sufix,collisions,cdr,positives,negatives");
				for (int i = 3; i < totalLenth; i++)
				{
					for (int j = 0; j <= i; j++)
					{
						window.prefix = j;
						window.suffix = i - j;
						Console.WriteLine("Testing window: prefix={0} suffix={1}", window.prefix, window.suffix);
						// Muestrea
						Sample(window, negativeSamples, positiveSamples, rawObjects, byteSequences, overlaping, out cdr);
						report.WriteLine("{0},{1},{2},{3},{4},{5},{6}", i, window.prefix, window.suffix, overlaping.Count, cdr, positiveSamples.Count, negativeSamples.Count);
						negativeSamples.Clear();
						positiveSamples.Clear();
						byteSequences.Clear();
						overlaping.Clear();
					}
				}
				report.Close();
			}
		}

		private static void Sample(wnd window, ByteArraySet negativeSamples, ByteArraySet positiveSamples, IEnumerable<SequenceSegmentPair> rawObjects, List<byte[]> byteSequences, ByteArraySet overlapping, out int? cdr)
		{
			// muestrea pares
			foreach (var pair in rawObjects)
			{
				var byteSeq = UniprotHelper.TranslateAminoacidStringToByteString(pair.sequence);
				byteSequences.Add(byteSeq);
				var posIndex = pair.chain2Start - pair.chain1Start - window.prefix;
				SamplerHelper.SampleWithSlidingWindow(byteSeq, window.prefix + window.suffix, posIndex, negativeSamples, positiveSamples);
			}

			overlapping.UnionWith(negativeSamples);
			overlapping.IntersectWith(positiveSamples);
			Console.WriteLine("Positive set: {0}", positiveSamples.Count);
			Console.WriteLine("Negative set: {0}", negativeSamples.Count);
			Console.WriteLine("Colisiones {0}", overlapping.Count);

			cdr = EvaluateCollisionDynamicRange(byteSequences, overlapping);
			Console.WriteLine("Collision Dynamic Range: {0}", cdr);
		}

		/// <summary>
		/// Obtiene una medida de rango dinamico para las colisiones en una coleccion de secuencias.
		/// El rango dinamico que se calcula en esta funcion obtiene el mayor desplazamiento que tuvo una colision
		/// en todo el conjunto de secuencias. Esto brinda un indicador de la bondad de una configuracion de muestreo
		/// porque ayuda a identificar secuencias donde hay un posible mal etiquetado.
		/// </summary>          
		/// <example>
		/// sean colisiones C1={10,9,11} y C2={13,12,14} y el conjunto de secuencias SEQS esta formado por 4 secuencias.
		/// C1 ocurre en las secuencias de SEQS en estos indices I1={10,10,30,31}
		/// C2 ocurre en las secuencias de SEQS en estos indices I2={5,6,7,5}
		/// El desplazamiento maximo para C1 es MCO1=max(I1)-min(I1)=31-10=21
		/// El desplazamiento maximo para C2 es MCO2=max(I2)-min(I2)=7-5=2
		/// El desplazamiento maxmo para todas las colisiones es MCO=max(MCO1,MCO2)=21
		/// </example>
		static int? EvaluateCollisionDynamicRange(ICollection<byte[]> collection, ICollection<byte[]> collisions)
		{
			if (collisions.Count == 0) return null;
			var s = from collision in collisions
							from seq in collection
							// todas las ocurrencias de la colision
							from i in IndexOfAll(seq, collision)
							group i by collision into grp
							select grp.Max() - grp.Min();
			var co = s.Max();
			return co;
		}

		/// <summary>
		/// Encuentra todas las ocurrencias de <param name="pat"></param> en <param name="col"></param>
		/// </summary>
		static IEnumerable<int> IndexOfAll(byte[] col, byte[] pat)
		{
			for (int i = 0; i < col.Length - pat.Length + 1; i++)
			{
				bool found = true;
				for (int j = 0; j < pat.Length; j++)
				{
					if (col[i + j] != pat[j])
					{
						found = false;
						break;
					}
				}
				if (found) yield return i;
			}
		}
	}
}
